<?php
class Main_Db_Table_Abstract
{
	/**
	 * @var string
	 */
	protected $_name = null;
	
	/**
	 * @var string
	 */
	protected $_primary = null;
	
	/**
	 * @var Main_Db_Adapter_Abstract
	 */
	protected $_db;

	/**
	 * @var array
	 */
	protected $_fields;

	/**
	 * @var bool
	 */
	protected $_isTree = false;
	
	/**
	 * @var int
	 */
	const TREE_TOP = -1;
	
	/**
	 * @return void
	 */
	public function __construct() 
	{
		$this->_db = Main_Db::get();
		$pfx = Zend_Registry::getInstance()->tablePrefix;
		
		if ($this->_isTree && ($this->_primary != 'id')) {
			throw new Exception('Tree primary key must be "id"');
		}
	}
	
	/**
	 * @return void
	 */
	public function init() 
	{
	}
	
	/**
	 * @param array $data
	 * @return bool
	 */
	public function insert(array $data)
	{
		if ($this->_isTree) {
			// --- parent --- {{
			if (empty($data['parent_id'])) {
				$parentLft    = 0;
                $parentLevel  = 0;
                $data['parent_id'] = null;
			} else {
				$parent = $this->_db->fetchRow("
					SELECT 
						lft, level 
					FROM 
						{$this->_name} 
					WHERE 
						id = {$data['parent_id']}
				");
				$parentLft = $parent['lft'];
				$parentLevel  = !empty($parent['level']) ? $parent['level'] : 0; 
			}
			// --- parent --- }}
			
			// --- recalculate keys --- {{
			$this->_db->query("
				UPDATE 
					{$this->_name} 
				SET 
					lft = lft + 2 
				WHERE 
					lft > {$parentLft}
			");
            $this->_db->query("
            	UPDATE 
            		{$this->_name} 
            	SET 
            		rgt = rgt + 2 
            	WHERE rgt > {$parentLft}
            ");
            // --- recalculate keys --- }}		
			
            // --- item keys --- {{		
            $data['lft']    = $parentLft + 1;
            $data['rgt']    = $parentLft + 2;
            $data['level']  = $parentLevel + 1;
            // --- item keys --- }}
		}
		
		return $this->_db->insert($this->_name, $data);
	}
	
	public function moveNode($id, $newParentId = self::TREE_TOP, $afterId = null) 
	{
		// --- item --- {{ 
		$sql = "
			SELECT 
				lft, rgt, level 
			FROM 
				{$this->_name} 
			WHERE 
				id = {$id}
		";
		$item = $this->_db->fetchRow($sql);
		if (empty($item)) {		
			throw new Exception('Node not found');
		}					
		$lft = $item['lft'];
		$rgt = $item['rgt'];
		$level = $item['level'];
		// --- item --- }}
		
		// --- new parent ---{{
		if ($newParentId != self::TREE_TOP) { 
			$parent = $this->_db->fetchRow("
				SELECT 
					lft, rgt, level 
				FROM 
					{$this->_name} 
				WHERE 
					id = {$newParentId}
			");
			if (empty($parent)) {		
				throw new Exception('New parent not found');
			}			
			$newParentLft = $parent['lft'];
			$newParentLevel = $parent['level'];
		} else {
			$newParentLevel = 1;
			$newParentLft = 1;
		}	
		// --- new parent ---}}
		
		// --- after id --- {{
		if (null != $afterId) {
			
			$afterItem = $this->_db->fetchRow("
				SELECT 
					lft, rgt, level, parent_id 
				FROM 
					{$this->_name} 
				WHERE 
					id = {$afterId}
			");
			$afterItemRgt = $afterItem['rgt']; 		
		} else {
			$afterItemRgt = $newParentLft;
		}
		// --- after id --- }}
		
		$levelDelta = $newParentLevel - $level + 1;  
		$treeDelta = $rgt - $lft + 1; // branch element count
		
		// --- descedants --- {{
		$branchList = $this->_db->fetchAll("
			SELECT 
				id 
			FROM 
				{$this->_name}
			WHERE
				lft >= $lft AND rgt <= $rgt	
		");
		$branchIds = Main_Function::expandList($branchList, 'id', 'id');
		// --- descedants --- }}
		
		// --- recalculate keys --- {{
		if ($afterItemRgt > $rgt) {
			$editDelta = $afterItemRgt - $lft + 1 - $treeDelta;

			$this->_db->query("
				UPDATE 
					{$this->_name} 
				SET 
					rgt = rgt - $treeDelta 
				WHERE 
					rgt > $rgt AND rgt <= $afterItemRgt"
			);		
			$this->_db->query("
				UPDATE 
					{$this->_name}
				 SET 
				 	lft = lft - $treeDelta 
				 WHERE 
				 	lft > $rgt AND lft <= $afterItemRgt
			");
			// --- recalculate keys for descedants --- {{		
			$this->_db->query("
				UPDATE 
					{$this->_name} 
				SET 
					lft = lft + $editDelta, 
					rgt = rgt + $editDelta, 
					level = level + $levelDelta 
				WHERE id IN (".implode(', ', $branchIds).")"
			);
			// --- recalculate keys for descedants --- }}
		} else {
			$editDelta = $afterItemRgt - $lft + 1;
			
			$this->_db->query("
				UPDATE 
					{$this->_name}
				SET 
					rgt = rgt + $treeDelta 
				WHERE 
					rgt < $lft AND rgt > $afterItemRgt"
			);				
			$this->_db->query("
				UPDATE 
					{$this->_name} 
				SET 
					lft = lft + $treeDelta 
				WHERE 
					lft < $lft AND lft > $afterItemRgt
			");
			// --- recalculate keys for descedants --- {{
			$this->_db->query("
				UPDATE 	
					{$this->_name}
				SET 
					lft = lft + $editDelta, 
					rgt = rgt + $editDelta, 
					level = level + $levelDelta 
				WHERE 
					id IN (".implode(', ', $branchIds).")
			");
			// --- recalculate keys for descedants --- {{		
		}
		// --- recalculate keys --- }}
		$this->_db->query("
			UPDATE 
				{$this->_name} 
			SET 
				parent_id=" .( ($newParentId != self::TREE_TOP) ? $newParentId : 'NULL' )." 
			WHERE id={$id}
		");
				
				
		
	}
	
	
	/**
	 * @param array $data
	 * @param string $where
	 * @return bool
	 */
	public function update(array $data, $where = '') 
	{
		if ($this->_isTree) {
			foreach (array('lft', 'rgt', 'level', 'parent_id') as $field) {
				if (isset($data[$field])) {
					unset($data[$field]);
				}
			}
		}
		return $this->_db->update($this->_name, $data, $where);
	}
	
	/**
	 * @param int $id
	 * @return bool
	 */
	public function deleteBranch($id) 
	{
		// --- item --- {{ 
		$item = $this->_db->fetchRow("
			SELECT 
				lft, rgt 
			FROM 
				{$this->_name} 
			WHERE 
				id = {$id}
		");
		$lft = $item['lft'];
		$rgt = $item['rgt'];
		// --- item --- }}
		
		// --- delete branch with item --- {{
		$this->_db->query("
			DELETE FROM 
				{$this->_name} 
			WHERE 
				lft >= {$lft} AND rgt <= {$rgt}
		");
		// --- delete branch with item --- }}		

		// --- recalculate keys --- {{
		$shiftDelta = $rgt - $lft + 1; // branch element count
				
		$this->_db->query("
			UPDATE 
				{$this->_name} 
			SET 
				lft = lft - $shiftDelta,
				rgt = rgt - $shiftDelta 
			WHERE 
				rgt > {$rgt} AND lft > $lft 
		");
		$this->_db->query("
			UPDATE 
				{$this->_name} 
			SET 
				rgt = rgt - $shiftDelta 
			WHERE 
				rgt > {$rgt} AND lft < $lft 
		");		
		// --- recalculate keys --- }}
		return true;	
	}
	
	
	/**
	 * @param string $where
	 * @return bool
	 */
	public function delete($where = '')
	{
		if (! $this->_isTree) {
			return $this->_db->delete($this->_name, $where);
		}
		return false;  
	}
	
	/**
	 * @return Main_Db_Adapter_Abstract
	 */
	public function getAdapter()
	{
		return $this->_db;
	}
	
	/**
	 * @param string $str
	 * @param bool $isQuote
	 * @return string
	 */
	public function escape($str, $isQuote = false)
	{
		return $this->_db->escape($str, $isQuote);
	}
	
	/**
	 * @return int
	 */
	public function lastInsertId() 
	{
		return $this->_db->lastInsertId($this->_name, $this->_primary);
	}
	
	/**
	 * @param array $data
	 * @param array $fields
	 * @param array $quoteFields
	 * @return array
	 */
	public function beforeSave($data, $fields, $quoteFields = array()) 
	{
		$dataOk = array();
		if (is_array($fields)) {
			foreach ($fields as $field) {
	    		$isQuoteField = in_array($field, $quoteFields);
	    		if (isset($data[$field])) {
					$dataOk[$field] = $this->escape($data[$field], $isQuoteField);
	    		}	
	    	}
		}	
    	return $dataOk;
	}
}
